package ccpvspiral1;

import java.util.ArrayList;


// should make getaltruims scale linearly between values in years where we don't have data for


public class Epoch {
	private String name;
    private ArrayList<DatedValues<Integer>> values = new ArrayList<DatedValues<Integer>>();
    private ArrayList<Integer> characteristics = new ArrayList<Integer>();;
    private ArrayList<ArrayList<DatedValues<Double>>> altruisms = new ArrayList<ArrayList<DatedValues<Double>>>(); 
    private int geolocation = 0;
   
    public Epoch() {
    	
    }
    


    
    public void updateAltruism(int group_index, int year, double value) {
    	if (group_index > (altruisms.size()-1)) {
    		addAltruism(group_index, year, value);
    	} else {
    		for (int i = 0; i<altruisms.get(group_index).size(); i++) {
    		  if (altruisms.get(group_index).get(i).getDate() == year) {
    			  altruisms.get(group_index).get(i).getValues().set(0, value);
    		  }
    		}
    	}
    }
    
    
    public void addAltruism(int group_index, int year, double value) {
    	DatedValues<Double> dv = new DatedValues<Double>(year,value);
    	if (group_index > (altruisms.size()-1)) {
    		for (int i = altruisms.size()-1; i<group_index; i++) {
    			altruisms.add(new ArrayList<DatedValues<Double>>());
    		}
    	}
    	altruisms.get(group_index).add(dv);
    }
    
    public DatedValues<Double> getAltruism(int group_index, int year) {
    	if (group_index > altruisms.size()-1) {
    		return new DatedValues<Double>(year,0.0); // default return 0
    	}
    	for (DatedValues<Double> dv: altruisms.get(group_index)) {
    		if (dv.getDate() == year) {
    			return dv;
    		}
    	}
    	return new DatedValues<Double>(year,0.0); // default return 0
    }
    
    public Epoch(String name, int location, ArrayList<Integer> character, DatedValues<Integer> dv) {
    	this.name = name;
    	this.setGeolocation(location);
    	this.setCharacteristics(character);
    	values.add(dv);
    	
    }
    public Epoch(String name, int location, ArrayList<DatedValues<Integer>> values, ArrayList<Integer> characteristics) {
    	this.setGeolocation(location);
    	this.setName(name);
    	this.setValues(values);
    	this.setCharacteristics(characteristics);
    }
    
    public void addDatedValue(int date, int value) {
    	this.values.add(new DatedValues<Integer>(date, value));
    }
     
    public void addDatedValue(DatedValues<Integer> dv) {
    	this.values.add(new DatedValues<Integer>(dv.getDate(),dv.getQty(),dv.getValues()));
    }

	/**
	 * @param name the name to set
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * @param values the values to set
	 */
	public void setValues(ArrayList<DatedValues<Integer>> values) {
    	for (DatedValues<Integer> dv: values) {
    		this.values.add(new DatedValues<Integer>(dv.getDate(),dv.getQty(),dv.getValues()));
    	}
	}
	/**
	 * @return the values
	 */
//	public ArrayList<DatedValues<Integer>> getValues() {
//		return values;
//	}

	/**
	 * @param characteristics the characteristics to set
	 */
	public void setCharacteristics(ArrayList<Integer> characteristics) {
		for (int i: characteristics) {
    		this.characteristics.add(i);	
    	}
	}

	/**
	 * @return the characteristics
	 */
	public ArrayList<Integer> getCharacteristics() {
		return characteristics;
	}
	
	public int getCharacteristicAt(int i) {
		return characteristics.get(i);
	}
	
	/**
	 * Returns the value corresponding to the date represented in integer form. This is a probably inefficient way of accomplishing this, however it'll suffice for now and for data sets with few dates.
	 * @param date
	 * @return
	 */
	public int getValueAt(int date, int index) {
		int value = 0;
		for (DatedValues<Integer> dv: values) {
			if (dv.getDate() == date) {
				return dv.getValues().get(index);
			}
		}
		return value;
	}
	
/*    public int getQtyAt(int year) {
      for (DatedValues dv: values) {
    	 // System.out.print(dv.getDate() + "==" + year + "?");
    	  if (dv.getDate() == year) {
    	//	  System.out.println(" Yes");
    		  return dv.getQty();
    	  }
      }
      return 0;
    }
	
	*/
	
	
	/**
	 * Returns num number of values closest to the specified date. 
	 * @param date
	 * @param num
	 * @return
	 */
	public ArrayList<DatedValues<Integer>> getClosestValuesAt(int date, int num) {
		ArrayList<DatedValues<Integer>> retValues = new ArrayList<DatedValues<Integer>>();
		
		DatedValues<Integer> dv1 = getLowerValues(date);
		if (dv1.getDate()!=0) retValues.add(dv1);
		DatedValues<Integer> dv2 = getHigherValues(date);
		if (dv2.getDate()!=0) retValues.add(dv2);
		
		
		
		
		
		
		/*
		int index1=0, index2 = 0;
		boolean direction = true;
		// if num > size of values, throw error?
		// if date is first element
		// if date is last element
		// expand one at a time in both directions of index until bounds or num are met
		// favors lower end for even number of numbers
		
		
		
		
		for (int i=0; i< this.values.size(); i++) {
			if (values.get(i).getDate() >= date) { 
				index1 = i; 
				index2 = i;
				break;
			} 
		}
		
		
		for (int i=0; i<num-1; i++) {
			if (direction) {
				if (index1 != 0) {
					index1--;
				} else {
					if (index2 != this.values.size()-1) {
						index2++;
					}
				}
				direction = !direction;
			} else {
				if (index2 != this.values.size()-1) {
					index2++;
				} else {
					if (index1 != 0) {
						index1--;
					}
				}
				direction = !direction;
			}
		}
		int index = 0;
		
		assert(index2-index1+1 == num);
		
		for (int i = index1; i <= index2; i++) {
			//retValues.add(getValuesAt(i).get(m_index));
			retValues.add(values.get(i));
			index++;
		}
		
		*/
		
		return retValues;
	}
	
	private DatedValues<Integer> getLowerValues(int date) {
		DatedValues<Integer> dv = new DatedValues<Integer>();
		for (int i=0; i< this.values.size(); i++) {
			if (values.get(i).getDate() < date) {
				dv = values.get(i);
			} else {
				return dv;
			}
		}
		return dv;
	}
	
	private DatedValues<Integer> getHigherValues(int date) {
	
		for (int i=0; i< this.values.size(); i++) {
			if (values.get(i).getDate() > date) {
				return values.get(i);
			} 
		}
		DatedValues<Integer> dv = new DatedValues<Integer>();
		return dv;
	}

	public String toString() {
		String st = "(";
		st = st + name;
		for (Integer value: characteristics) {
			  st = st + ", " + value.toString();	
		}
		
		for (DatedValues<Integer> value: values) {
		  st = st + ", " + value.toString();	
		}
		
		st = st + ")";
		return st;
	}
	
	public double getLinearPopulation(int startYear, int date) {
		if (values.isEmpty()) return 0;
		
		// If the value for that exact date is known, return it
		for (DatedValues<Integer> dv: values) {
			if (dv.getDate() == date) {
				return (double)dv.getQty();
			}
		}

		// If we can't find a pair of values to enclose the requested date, 
		// then assume (startdate, 0) is a data point
		
		
		// Value for that exact date was not found, find the nearest pair of values if possible
		ArrayList<Integer> value = new ArrayList<Integer>();
		
		ArrayList<DatedValues<Integer>> closest = this.getClosestValuesAt(date, 2);
		if (closest.size() == 0) return 0;
		double slope = 0;
		double yint = 0;
		if (closest.size() == 1) {
			slope = (closest.get(0).getQty()-0)/(closest.get(0).getDate()-startYear);
			yint = -1*slope*startYear+0;
			return slope*date+yint;//y=mx+b
		}

		// we have a pair of data points, wheee!
		slope = (closest.get(1).getQty()-closest.get(0).getQty())/(closest.get(1).getDate()-closest.get(0).getDate());
		yint = -1*slope*closest.get(0).getDate()+closest.get(0).getQty();
		
		return slope*date+yint;//y=mx+b
		
	}

	
	public double getLinearValueAt(int date, int index) {
		if (values.isEmpty()) return 0;
		
		for (DatedValues<Integer> dv: values) {
			if (dv.getDate() == date) {
				return (double)dv.getValues().get(index);
			}
		}
		ArrayList<Integer> value = new ArrayList<Integer>();
		
		ArrayList<DatedValues<Integer>> closest = this.getClosestValuesAt(date, 2);
		double slope = (closest.get(1).getValueAt(index)-closest.get(0).getValueAt(index))/(closest.get(1).getDate()-closest.get(0).getDate());
		double yint = -1*slope*closest.get(0).getDate()+closest.get(0).getValueAt(index);
		
		return slope*date+yint;//y=mx+b
		
	}

	/**
	 * @param geolocation the geolocation to set
	 */
	public void setGeolocation(int geolocation) {
		this.geolocation = geolocation;
	}

	/**
	 * @return the geolocation
	 */
	public int getGeolocation() {
		return geolocation;
	}

}
